// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_particle_renderer.h"
#include "mk_engine.h"
#include "mk_error.h"
#include "mk_resource_manager.h"
#include "mk_render_device.h"
#include "mk_camera.h"

namespace MK3DE
{
	static const char* __TECH_SRC_ALPHA_DST_INVALPHA = "srcAlphaDstInvAlpha";
	static const char* __TECH_SRC_ALPHA_DST_ONE = "srcAlphaDstOne";

	static const BYTE __EFFECT[] = "\
		float4x4 matWorldViewProj;\
		void Vert3D(float4 iPos : POSITION,\
		float4 iColor : COLOR,\
		float2 iTex : TEXCOORD,\
		out float4 oPos : POSITION,\
		out float4 oColor : COLOR,\
		out float2 oTex : TEXCOORD)\
		{\
			oPos = mul(iPos, matWorldViewProj);\
			oColor = iColor;\
			oTex = iTex;\
		}\
		technique srcAlphaDstInvAlpha\
		{\
			pass P0\
			{\
				CullMode		= None;\
				Lighting		= False;\
				ZEnable			= True;\
				ZWriteEnable = False;\
				AlphaBlendEnable = True;\
				SrcBlend = SrcAlpha;\
				DestBlend = InvSrcAlpha;\
				AlphaTestEnable = False;\
				ColorOp[0] = Modulate;\
				AlphaOp[0] = Modulate;\
				ColorArg1[0] = Texture;\
				ColorArg2[0] = Diffuse;\
				AlphaArg1[0] = Texture;\
				AlphaArg2[0] = Diffuse;\
				ColorOp[1] = Disable;\
				AlphaOp[1] = Disable;\
				VertexShader = compile vs_2_0 Vert3D();\
			}\
		}\
		technique srcAlphaDstOne\
		{\
			pass P0\
			{\
				CullMode		= None;\
				Lighting		= False;\
				ZEnable			= True;\
				ZWriteEnable = False;\
				AlphaBlendEnable = True;\
				SrcBlend = SrcAlpha;\
				DestBlend = One;\
				AlphaTestEnable = False;\
				ColorOp[0] = Modulate;\
				AlphaOp[0] = Modulate;\
				ColorArg1[0] = Texture;\
				ColorArg2[0] = Diffuse;\
				AlphaArg1[0] = Texture;\
				AlphaArg2[0] = Diffuse;\
				ColorOp[1] = Disable;\
				AlphaOp[1] = Disable;\
				VertexShader = compile vs_2_0 Vert3D();\
			}\
		}\
		";

	CParticleRenderer::CParticleRenderer() :
	_shader(NULL),
		_renderInput(NULL),
		_vertexBuffer(NULL),
		_vertexBufferPtr(NULL),
		_currentParticleCount(0)
	{

	}

	CParticleRenderer::~CParticleRenderer()
	{
		Destroy();
	}

	BOOL CParticleRenderer::Create(DWORD preAlocateParticleCount)
	{
		BOOL ret = FALSE;

		CRenderDevice* rd = GetEngine()->GetRenderDevice();
		CResourceManager* rm = GetEngine()->GetResourceManager();

		ret = rm->GetShaderFromMemory(__EFFECT, sizeof(__EFFECT), _shader);
		MK_PROCESS_ERROR(ret);

		ret = rm->GetVertexBuffer(sizeof(VERT_3D_COL_TEX1), preAlocateParticleCount * 6, (HARDWARE_BUFFER_USAGE)(HBU_DYNAMIC|HBU_WRITE_ONLY), _vertexBuffer);
		MK_PROCESS_ERROR(ret);

		RenderInputElemDesc rie[] = {
			RenderInputElemDesc(SU_POSITION, 0, 0, 0),
			RenderInputElemDesc(SU_COLOR, 0, 12, 0),
			RenderInputElemDesc(SU_TEXCOORD, 0, 16, 0)
		};

		ret = rd->CreateRenderInput(rie, 3, _renderInput);
		MK_PROCESS_ERROR(ret);

		ret = _renderInput->SetVertexBuffer(0, _vertexBuffer);
		MK_PROCESS_ERROR(ret);

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CParticleRenderer::Destroy()
	{
		CRenderDevice* rd = GetEngine()->GetRenderDevice();
		SAFE_RELEASE(_shader);
		SAFE_RELEASE(_vertexBuffer);
		rd->DestroyRenderInput(_renderInput);
		return FALSE;
	}

	BOOL CParticleRenderer::RenderAll(CTexture2D* texture, D3DXMATRIX& mat, PARTICLE_RENDER_BLEND_MODE blendMode)
	{
		BOOL ret = FALSE;
		CRenderDevice* rd = GetEngine()->GetRenderDevice();

		ret = _vertexBuffer->Unlock();
		MK_PROCESS_ERROR(ret);

		if (_currentParticleCount < 1)
			return TRUE;

		ret = rd->BindRenderInput(_renderInput);
		MK_PROCESS_ERROR(ret);

		ret = _shader->SetMatrix(SCN_MAT_WORLD_VIEW_PROJ, &mat);
		MK_PROCESS_ERROR(ret);

		ret = rd->SetTexture(0, texture);
		MK_PROCESS_ERROR(ret);

		DWORD passNum;
		if (blendMode == PRBM_SRC_ALPHA_DST_INVALPHA)
			ret = _shader->TechniqueBegin(__TECH_SRC_ALPHA_DST_INVALPHA, passNum);
		else if (blendMode == PRBM_SRC_ALPHA_DST_ONE)
			ret = _shader->TechniqueBegin(__TECH_SRC_ALPHA_DST_ONE, passNum);
		else
			ASSERT(0);

		MK_PROCESS_ERROR(ret);

		for (DWORD pass = 0; pass < passNum; ++pass)
		{
			if (_shader->PassBegin(pass))
			{
				ret = rd->DrawPrimitive(PT_TRIANGLELIST, 0, 0, _currentParticleCount * 6, 0, _currentParticleCount * 2);
				MK_PROCESS_ERROR(ret);

				_shader->PassEnd();
			}
		}

		_shader->TechniqueEnd();

		_currentParticleCount = 0;

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CParticleRenderer::PrepareRender(DWORD particleCount)
	{
		BOOL ret = FALSE;
		if (particleCount * 6 > _vertexBuffer->GetSize())
		{
			CResourceManager* rm = GetEngine()->GetResourceManager();

			SAFE_RELEASE(_vertexBuffer);

			ret = rm->GetVertexBuffer(sizeof(VERT_3D_COL_TEX1), particleCount * 6, (HARDWARE_BUFFER_USAGE)(HBU_DYNAMIC|HBU_WRITE_ONLY), _vertexBuffer);
			MK_PROCESS_ERROR(ret);

			ret = _renderInput->SetVertexBuffer(0, _vertexBuffer);
			MK_PROCESS_ERROR(ret);
		}

		_currentParticleCount = 0;
		ret = _vertexBuffer->Lock((void*&)_vertexBufferPtr, 0, 0, LO_DISCARD);
		MK_PROCESS_ERROR(ret);

		ret = TRUE;
Exit0:
		return ret;
	}

	BOOL CParticleRenderer::PushParticle(Particle& p, int textureFrame, DWORD textureCutting, float textureSize)
	{        
		float halfWidth = p.Size.x * 0.5f;
		float halfHeight = p.Size.y * 0.5f;

		CCamera* camera = GetEngine()->GetCurrentCamera();
		D3DXVECTOR3 upDir = camera->GetUpDirection() * halfHeight;
		D3DXVECTOR3 rightDir = camera->GetRightDirection() * halfWidth;
		D3DXVECTOR3 lookDir = camera->GetLookDirection();

		if (abs(p.Rotation) > MK_EPSILON)
		{
			D3DXMATRIX rot;
			D3DXMatrixRotationAxis(&rot, &lookDir, p.Rotation);
			D3DXVec3TransformNormal(&upDir, &upDir, &rot);
			D3DXVec3TransformNormal(&rightDir, &rightDir, &rot);
		}

		D3DXVECTOR3 p1(p.Position.x - rightDir.x + upDir.x, p.Position.y - rightDir.y + upDir.y, p.Position.z - rightDir.z + upDir.z);
		D3DXVECTOR3 p2(p.Position.x + rightDir.x + upDir.x, p.Position.y + rightDir.y + upDir.y, p.Position.z + rightDir.z + upDir.z);
		D3DXVECTOR3 p3(p.Position.x + rightDir.x - upDir.x, p.Position.y + rightDir.y - upDir.y, p.Position.z + rightDir.z - upDir.z);
		D3DXVECTOR3 p4(p.Position.x - rightDir.x - upDir.x, p.Position.y - rightDir.y - upDir.y, p.Position.z - rightDir.z - upDir.z);

		if (p.TextureFrame >= textureFrame)
			p.TextureFrame = textureFrame - 1;

		DWORD tx = p.TextureFrame % textureCutting;
		DWORD ty = p.TextureFrame / textureCutting;
		float left = tx * textureSize;
		float right = left + textureSize;
		float top = ty * textureSize;
		float bottom  = top + textureSize;

		_vertexBufferPtr->pos = p1;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = left;
		_vertexBufferPtr->uv.y = top;
		_vertexBufferPtr++;

		_vertexBufferPtr->pos = p2;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = right;
		_vertexBufferPtr->uv.y = top;
		_vertexBufferPtr++;

		_vertexBufferPtr->pos = p3;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = right;
		_vertexBufferPtr->uv.y = bottom;
		_vertexBufferPtr++;

		_vertexBufferPtr->pos = p1;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = left;
		_vertexBufferPtr->uv.y = top;
		_vertexBufferPtr++;

		_vertexBufferPtr->pos = p3;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = right;
		_vertexBufferPtr->uv.y = bottom;
		_vertexBufferPtr++;

		_vertexBufferPtr->pos = p4;
		_vertexBufferPtr->color = p.Color;
		_vertexBufferPtr->uv.x = left;
		_vertexBufferPtr->uv.y = bottom;
		_vertexBufferPtr++;

		_currentParticleCount++;

		return TRUE;
	}
}